package edu.mit.csail.relo.jdt.properties;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IWorkbenchPropertyPage;

import edu.mit.csail.relo.jdt.ReloJDTPlugin;
import edu.mit.csail.relo.jdt.builder.asm.AsmBuildVisitor;
import edu.mit.csail.relo.jdt.util.UIUtils;

public abstract class TabbedFieldEditorOverlayPage
					extends FieldEditorPreferencePage
                    implements IWorkbenchPropertyPage {

	private String pageId;
	private IAdaptable element;
	private Composite page;
	private TabFolder folder;
	private Table jarTable;
	private Button selectAll;
	private Button deselectAll;
	private static final int BUTTON_WIDTH = 125;
	private static final int BUTTON_HEIGHT = 23;
	private static final int BUTTON_X = 1;
	private static final int BUTTON_Y = 30;
	private List<URL> classPathURLs;
	private List<URL> jreSysLib = new ArrayList<URL>();
	final ImageRegistry imageRegistry = new ImageRegistry();
	private static final String IMG_DEFAULT = "default";
	private static final String IMG_JAR = "jar";
	private static final String IMG_JAR_ALTERNATE = "jarAlternate";
	private static final String IMG_ZIP = "zip";
	private static final String IMG_JRE = "jre";
	private static final String FALSE = "false";
	private static final String TRUE = "true";
	private static final String JRE_SYSTEM_LIBRARY = "JRE System Library (Not currently supported)";
	//Not supported because building these jars results in an OutOfMemoryException
	
	static final Logger logger = ReloJDTPlugin.getLogger(AsmBuildVisitor.class);
	
	public TabbedFieldEditorOverlayPage(int style) {
		super(style);
	}
	
	public TabbedFieldEditorOverlayPage(String title, int style) {
		super(title, style);
	}

	public TabbedFieldEditorOverlayPage(String title, ImageDescriptor image, int style) {
	  super(title, image, style);
	}
	
	/**
	 * Create contents of property page
	 */
	@Override
	public Control createContents(Composite composite) {	
		pageId = "Relo Properties";
		page = new Composite(composite,SWT.NONE);
		GridLayout layout = new GridLayout(2, false);
		layout.marginHeight=0;
		page.setLayout(layout);
		GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
		data.heightHint=350;
		page.setLayoutData(data);
		
		addJarSelectionArea();
		return page;
	}
	
	/**
	 * Called when property page is opened
	 */
	public void setElement(IAdaptable element) {
		this.element = element;
	}

	public IAdaptable getElement() {
		return element;
	}
	
	/**
	 * Returns true if page opened was a property page
	 */
	public boolean isPropertyPage() {
		return getElement() != null;
	}	

	/**
	 * Determines where the newly constructed field editor will be placed
	 */
	@Override
	protected Composite getFieldEditorParent() {
		if(folder==null || folder.getItemCount()==0) {
			return super.getFieldEditorParent();
		}
		return (Composite) folder.getItem(folder.getItemCount()-1).getControl();
	}
	
	/** 
	 * Creates Select All and Deselect All buttons
	 */
	public void addSelectButtons() {	
		Composite composite = new Composite(page, SWT.NONE);
		composite.setLayoutData(new GridData(SWT.NONE, SWT.FILL, false, true));
			
		selectAll = new Button(composite, SWT.PUSH);
		selectAll.setSize(BUTTON_WIDTH, BUTTON_HEIGHT);
		selectAll.setText("Select All");
		GridData selectAllData = new GridData(GridData.FILL_HORIZONTAL);
		selectAll.setLayoutData(selectAllData);
		selectAll.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event event) {
				TableItem[] items = jarTable.getItems();
				for(TableItem item : items) {
					if(!(item.getText().equals(JRE_SYSTEM_LIBRARY))) {
						item.setChecked(true); // take this out of if statement once jdk supported
					}
				}
			}
		});

		deselectAll = new Button(composite, SWT.PUSH);
		deselectAll.setBounds(BUTTON_X, BUTTON_Y, BUTTON_WIDTH, BUTTON_HEIGHT);
		deselectAll.setText("Deselect All");
		deselectAll.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event e) {
				TableItem[] items = jarTable.getItems();
				for(TableItem item : items) {
					if(!(item.getImage().equals(imageRegistry.get(IMG_DEFAULT)))) {
						item.setChecked(false);
					}
				}
			}
		});
		composite.pack();
		composite.update();
	}
	
	/**
	 * Determines URLs in project's classpath and 
	 * adds them to a list for user selection
	 */
	public void addJarSelectionArea() {			
		IProject proj = ((IResource)element).getProject();
		IJavaProject project = JavaCore.create(proj);
		URL[] classPathURLArray = UIUtils.getProjectClasspath(project);
		classPathURLs = new ArrayList<URL>();
		for(URL url : classPathURLArray) {
			classPathURLs.add(url);
			if(url.toString().contains("jre")) {
				jreSysLib.add(url);
			}
		}
		addJarList();
		addSelectButtons();
	}

	/**
	 * Create the table that holds the list of jars for project
	 * Bug in eclipse: a 25x25 pixel or smaller image will cause 
	 * checkbox to decrease in size. Used own images that were 
	 * resized to be big enough to allow correctly sized checkboxes
	 */
	public void addJarList() {	
		final Table table = new Table(page, SWT.CHECK | SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.MULTI);
		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		table.setBackground(table.getDisplay().getSystemColor(SWT.COLOR_WHITE));
						
		Class thisClass = TabbedFieldEditorOverlayPage.class;
		ImageDescriptor defaultIcon = ImageDescriptor.createFromFile(thisClass, "default.png");
		ImageDescriptor jar = ImageDescriptor.createFromFile(thisClass, "jar.png");
		ImageDescriptor jarAlternate = ImageDescriptor.createFromFile(thisClass, "jarAlternate.png");
		ImageDescriptor zip = ImageDescriptor.createFromFile(thisClass, "zip.png");
		ImageDescriptor jre = ImageDescriptor.createFromFile(thisClass, "jre.png");
		imageRegistry.put(IMG_DEFAULT, defaultIcon);
		imageRegistry.put(IMG_JAR, jar);
		imageRegistry.put(IMG_JAR_ALTERNATE, jarAlternate);
		imageRegistry.put(IMG_ZIP, zip);
		imageRegistry.put(IMG_JRE, jre);
		
		List<String> jarFileNames = new ArrayList<String>();
		String token = new String();
		for(URL url : classPathURLs) {
			StringTokenizer tokenizer = new StringTokenizer(url.toString(), "/");
			while(tokenizer.hasMoreTokens()) {
	          token = tokenizer.nextToken();
			}
			jarFileNames.add(token);
		}
		
		int numberOfJars = jarFileNames.size();
		boolean addedJreItem = false;
		
		
		for(int i=0; i<numberOfJars; i++) {
			if(jreSysLib.contains(classPathURLs.get(i))) {
				if(!addedJreItem) {
					TableItem item = new TableItem(table, SWT.NONE);
					item.setImage(imageRegistry.get(IMG_JRE));
					item.setText(JRE_SYSTEM_LIBRARY);
					addedJreItem = true;
					
					// remove these two lines once jdk is supported		
					item.setChecked(false);
					item.setGrayed(true);
				}
			} 
			else {				
				TableItem  item = new TableItem(table, SWT.NONE);
				Image image = null;
				String name = jarFileNames.get(i);
				if(name.endsWith(".jar")) {
					image = imageRegistry.get(IMG_JAR);
				} else if (name.endsWith(".zip")) {
					image = imageRegistry.get(IMG_ZIP);
				} else {
					image = imageRegistry.get(IMG_DEFAULT);
					item.setChecked(true);
					item.setGrayed(true);
				}
				item.setImage(image);
				item.setText(name);	
				
				// Set checkboxes based on previously saved settings
				try {
					String check = ((IResource) getElement()).getPersistentProperty(
							new QualifiedName(pageId, name));
					if (TRUE.equals(check) || item.getImage().equals(imageRegistry.get(IMG_DEFAULT))) {
						item.setChecked(true);
					} else {
						item.setChecked(false);
					}
				} catch (CoreException e) {
					logger.error("Could not get property.", e);
				}
			}
		}
		
		final CheckboxTableViewer checkView = new CheckboxTableViewer(table);		
		checkView.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				for(TableItem item : table.getItems()) {
					if(item.getImage().equals(imageRegistry.get(IMG_DEFAULT))) {
						item.setChecked(true);
						item.setGrayed(true);
					}
					
					//remove this once jdk supported
					if(item.getText().equals(JRE_SYSTEM_LIBRARY)) {
						item.setChecked(false);
						item.setGrayed(true);
					}
				}
			}
		});
		
		table.pack();
		table.update();
		jarTable = table;
	}
	
	/**
	 * When user clicks okay, build the selected jars
	 */
	@Override
	public boolean performOk() {
		boolean result = super.performOk();
	    if (result && isPropertyPage()) {
			IResource resource = (IResource) getElement();
	    	List<URL> mustBeBuilt = new ArrayList<URL>();
	    	List<URL> mustBeRemoved = new ArrayList<URL>();
	    	TableItem[] items = jarTable.getItems();
	    	
	    	for(TableItem item : items) {
	    		boolean itemSelected = false;
				String text = item.getText();
				String check = "";
				try {
					check = ((IResource) getElement()).getPersistentProperty(
									new QualifiedName(pageId, text));
				} catch (CoreException e) {
					logger.error("Could not set property; ", e);
				}
	    		if(item.getChecked() && !item.getGrayed()) {
	    			itemSelected = true;
	    			if(!(TRUE.equals(check))) {
	    				if(text.equals(JRE_SYSTEM_LIBRARY)) {
	    					for(URL url : jreSysLib) {
	    						mustBeBuilt.add(url);
	    					}    					
	    				} else {
	    					for(URL url : classPathURLs) {
	    						String texts1 = text + "/";
	    						String texts2 = text + "\\";
	    						if(url.toString().endsWith(text) || url.toString().endsWith(texts1) 
	    								|| url.toString().endsWith(texts2)) {
	    							mustBeBuilt.add(url);
	    						}
	    					}	    			
	    				}
	    			}
				} else if(!item.getChecked() && !item.getGrayed()) {
					if(TRUE.equals(check)) {
	    				if(text.equals(JRE_SYSTEM_LIBRARY)) {
	    					for(URL url : jreSysLib) {
	    						mustBeRemoved.add(url);
	    					}    					
	    				} else {
	    					for(URL url : classPathURLs) {
	    						String texts1 = text + "/";
	    						String texts2 = text + "\\";
	    						if(url.toString().endsWith(text) || url.toString().endsWith(texts1) 
	    								|| url.toString().endsWith(texts2)) {
	    							mustBeRemoved.add(url);
	    						}
	    					}	    			
	    				}
	    			}
				}
				// Save state of checkboxes in project properties
				try {
					if(itemSelected) {
						resource.setPersistentProperty(new QualifiedName(pageId, text), TRUE);
					} else {
						resource.setPersistentProperty(new QualifiedName(pageId, text), FALSE);
					}
				} catch (CoreException e) {
					logger.error("Could not set property; ", e);
				}
	    	}
	    	
	    	try {
	    		IProject project = ((IResource)element).getProject();
	    		project.setSessionProperty(new QualifiedName(AsmBuildVisitor.JARS_TO_BUILD, AsmBuildVisitor.JARS_TO_BUILD), mustBeBuilt);
	    		project.setSessionProperty(new QualifiedName(AsmBuildVisitor.JARS_TO_REMOVE, AsmBuildVisitor.JARS_TO_REMOVE), mustBeRemoved);
	    		project.touch(null);
	    	} catch(Exception e) {
	    		logger.error("Could not build. ", e);
	    	}
	    }
	    return result;
	  }
}